/*
 *  Cero Project - Copyright   2006 The Cero Developement Team 
 *  (Michael Laguerre, Camille Roux, Matthieu Segret, Mathieu Sivade) 
 * 
 *  This program is free software; you can redistribute it and/or modify it 
 *  under the terms of the GNU General Public License as published by the Free 
 *  Software Foundation; either version 2 of the License, or (at your option) 
 *  any later version.
 * 
 *  This program is distributed in the hope that it will be useful, but 
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
 *  for more details.
 */

package org.ceroproject.games.base;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Vector;

import org.ceroproject.SortableList;
import org.ceroproject.Sorter;
import org.ceroproject.games.Action;
import org.ceroproject.games.ActionListener;
import org.ceroproject.games.ActionRule;
import org.ceroproject.games.Game;
import org.ceroproject.games.Round;
import org.ceroproject.games.RoundEvent;
import org.ceroproject.games.RoundListener;
import org.ceroproject.games.Rule;
import org.ceroproject.games.StopCondition;


public class RoundBase implements Round {
	/** The actions to execute */
	private Vector<Action> actions = new Vector<Action>();

	/** The collection of the stop conditions for this round */
	private Collection<StopCondition> stopConditions = new HashSet<StopCondition>();

	/** The collection of listeners to this round */
	private Collection<RoundListener> roundListeners = new HashSet<RoundListener>();

	/** The rules to use in this round */
	private Collection<ActionRule> actionRules = new HashSet<ActionRule>();

	private Collection<ActionListener> actionListeners = new HashSet<ActionListener>();

	/** The game in which the round occurs */
	private Game game;

	/** Subrounds sequentialy called by this round */
	private SortableList<Round> subRounds = new SortableList<Round>();

	/** The round's dedicated thread */
	private RoundRunner runner = new RoundRunner();

	/** The subRound really going on */
	private Round currentRound = null;

	public void start(Game game) {
		this.game = game;
		runner.run();
		this.game = null;
	}

	/**
	 * Returns if one of the StopCondition is true
	 * 
	 * @return true if one of the StopConditions is true, false otherwise
	 */
	private boolean CheckRoundStopCondition() {
		for (StopCondition cond : stopConditions) {
			if (cond.shouldStop(game))
				return true;
		}
		return false;
	}

	public void addRoundStopCondition(StopCondition condition) {
		stopConditions.add(condition);
	}

	public void removeRoundStopCondition(StopCondition condition) {
		stopConditions.remove(condition);
	}

	public void registerAction(Action act) {
		if (currentRound != null) {
			currentRound.registerAction(act);
		} else {
			// we're the active round
			// TODO use semaphore
			actions.add(act);
		}
	}

	public void addRoundListener(RoundListener listener) {
		roundListeners.add(listener);
	}

	public void removeRoundListener(RoundListener listener) {
		roundListeners.remove(listener);
	}

	/**
	 * Tells the RoundListeners the round has started
	 */
	private void notifyRoundStart() {
		Collection<RoundListener> roundListeners = new SorterRulesFirst<RoundListener>()
				.sort(this.roundListeners);
		for (RoundListener list : roundListeners) {
			// using a new event in case the last listener modified it
			RoundEvent event = new RoundEvent(game, this);
			list.roundStart(event);
		}
	}

	/**
	 * Tells the RoundListeners the round has ended
	 */
	private void notifyRoundEnd() {
		Collection<RoundListener> roundListeners = new SorterRulesFirst<RoundListener>()
				.sort(this.roundListeners);
		for (RoundListener list : roundListeners) {
			// using a new event in case the last listener modified it
			RoundEvent event = new RoundEvent(game, this);
			list.roundEnd(event);
		}
	}

	private boolean validateAction(Action act) {
		// TODO : add a way to sort the rules using a Sorter, to prevent
		// conflicting rules
		boolean allowed = false;
		for (ActionRule rul : actionRules) {
			try {
				if (rul.ruledAction() == null
						|| rul.ruledAction().isInstance(act)) {
					if (rul.allowsAction(act))
						allowed = true;
					if (rul.forbidsAction(act))
						return false;
				}
			} catch (Exception e) {
				// couldn't instanciate the action (bad package)
			}
		}
		return allowed;
	}

	public SortableList<Round> getSubRounds() {
		return subRounds;
	}

	/**
	 * Runs the round. This thread will be the one running and making the game
	 * progress. It receives actions and hooks them into the game's dedicated
	 * thread (this thread, precisely).
	 * 
	 * @author Telem
	 */
	private class RoundRunner implements Runnable {
		private static final long SLEEP_DURATION = 1000000L;

		/**
		 * Runs the main part of the round
		 */
		public void run() {
			actions.clear();
			notifyRoundStart();

			// we'll loop until a stop condition is checked
			while (!CheckRoundStopCondition()) {
				// 2 different modes : either there are subrounds, which get
				// called one after another, either there is none and we loop
				// here
				if (subRounds.size() > 0) {
					currentRound = subRounds.get(0);
					startRound(currentRound);
					for (int i = 1; i < subRounds.size(); i++) {
						// FIXME : il sert � quoi ce synchronized ?
						synchronized (currentRound) {
							currentRound = subRounds.get(i);
						}
						startRound(currentRound);
					}
					currentRound = null;
				} else {
					// we wait for an action to come in
					if (actions.size() > 0)
						pumpAction();
					else {
						try {
							Thread.sleep(SLEEP_DURATION);
						} catch (InterruptedException e) {
							// pumps one action
							pumpAction();
						}
					}
				}
			}

			notifyRoundEnd();
		}

		private void pumpAction() {
			// TODO use semaphore instead of synchronized
			Action act;
			if (actions.size() > 0) {
				act = actions.get(0);
				notifyActionPlayed(act);
				if (validateAction(act)) {
					act.executeAction();
					notifyActionExecuted(act);
				} else
					notifyActionFailed(act);
				actions.remove(act);
			}
		}

		private void startRound(Round r) {
			for (ActionListener actlist : actionListeners)
				r.addActionListener(actlist);
			for (ActionRule actrule : actionRules)
				r.addActionRule(actrule);
			r.start(game);
		}

	}

	public boolean addActionListener(ActionListener listener) {
		if (currentRound != null)
			currentRound.addActionListener(listener);
		return actionListeners.add(listener);
	}

	public boolean removeActionListener(ActionListener listener) {
		if (currentRound != null)
			currentRound.removeActionListener(listener);
		return actionListeners.remove(listener);
	}

	/**
	 * Notifies each action listener listening to the type of act of the event
	 * "onPlayerPlayed"
	 * 
	 * @param act
	 *            The action which was played
	 */
	private void notifyActionPlayed(Action act) {
		Collection<ActionListener> actionListeners = new SorterRulesFirst<ActionListener>()
				.sort(this.actionListeners);
		for (ActionListener list : actionListeners)
			if (list.listenedAction() == null
					|| list.listenedAction().isInstance(act))
				list.onPlayerPlayed(act);
	}

	/**
	 * Notifies each action listener listening to the type of act of the event
	 * "onExecuted"
	 * 
	 * @param act
	 *            The action which was executed
	 */
	private void notifyActionExecuted(Action act) {
		Collection<ActionListener> actionListeners = new SorterRulesFirst<ActionListener>()
				.sort(this.actionListeners);
		for (ActionListener list : actionListeners)
			if (list.listenedAction() == null
					|| list.listenedAction().isInstance(act))
				list.onExecuted(act);
	}

	/**
	 * Notifies each action listener listening to the type of act of the event
	 * "onFailed"
	 * 
	 * @param act
	 *            The action which was executed
	 */
	private void notifyActionFailed(Action act) {
		for (ActionListener list : actionListeners)
			if (list.listenedAction() == null
					|| list.listenedAction().isInstance(act))
				list.onFailed(act);
	}

	public boolean addActionRule(ActionRule rule) {
		return actionRules.add(rule);
	}

	public boolean removeActionRule(ActionRule rule) {
		return actionRules.remove(rule);
	}

	/**
	 * This Sorter puts Rules first, in order to have other listener do their
	 * job once rules have been applied
	 * 
	 * @author Telem
	 */
	private static class SorterRulesFirst<DataType> implements Sorter<DataType> {
		public List<DataType> sort(Collection<DataType> data) {
			List<DataType> l = new ArrayList<DataType>();
			for (DataType al : data) {
				if (al instanceof Rule)
					l.add(0, al);
				else
					l.add(l.size(), al);
			}
			return l;
		}
	}
}
